use std::any::Any;
use std::marker::{PhantomData, PhantomPinned};
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use crate::co;
use crate::decl::*;
use crate::guard::*;
use crate::gui::{*, events::*, privs::*, spec::*};
use crate::msg::*;
use crate::prelude::*;
struct Obj<T> { base: BaseNativeControl,
events: ListViewEvents,
context_menu: Option<HMENU>,
_pin: PhantomPinned,
_data: PhantomData<T>,
}
pub struct ListView<T: 'static = ()>(Pin<Arc<Obj<T>>>);
impl<T> Clone for ListView<T> { fn clone(&self) -> Self {
Self(self.0.clone())
}
}
unsafe impl<T> Send for ListView<T> {}
impl<T> AsRef<BaseNativeControl> for ListView<T> {
fn as_ref(&self) -> &BaseNativeControl {
&self.0.base
}
}
impl<T> GuiWindow for ListView<T> {
fn hwnd(&self) -> &HWND {
self.0.base.hwnd()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl<T> GuiChild for ListView<T> {
fn ctrl_id(&self) -> u16 {
self.0.base.ctrl_id()
}
}
impl<T> GuiChildFocus for ListView<T> {}
impl<T> GuiNativeControl for ListView<T> {}
impl<T> GuiNativeControlEvents<ListViewEvents> for ListView<T> {
fn on(&self) -> &ListViewEvents {
if *self.hwnd() != HWND::NULL {
panic!("Cannot add events after the control creation.");
} else if *self.0.base.parent().hwnd() != HWND::NULL {
panic!("Cannot add events after the parent window creation.");
}
&self.0.events
}
}
impl<T> ListView<T> {
#[must_use]
pub fn new(parent: &impl GuiParent, opts: ListViewOpts) -> Self {
let opts = auto_ctrl_id_if_zero(opts);
let ctrl_id = opts.ctrl_id;
let context_menu = opts.context_menu.as_ref().map(|h| unsafe { h.raw_copy() });
let new_self = Self(
Arc::pin(
Obj {
base: BaseNativeControl::new(parent, ctrl_id),
events: ListViewEvents::new(parent, ctrl_id),
context_menu,
_pin: PhantomPinned,
_data: PhantomData,
},
),
);
let self2 = new_self.clone();
parent.as_ref().before_user_on().wm_create_or_initdialog(move |_, _| {
self2.create(OptsResz::Wnd(&opts))?;
Ok(WmRet::NotHandled)
});
new_self.default_message_handlers(parent.as_ref(), ctrl_id);
new_self
}
#[must_use]
pub fn new_dlg(
parent: &impl GuiParent,
ctrl_id: u16,
resize_behavior: (Horz, Vert),
context_menu_id: Option<u16>,
) -> Self
{
let new_self = Self(
Arc::pin(
Obj {
base: BaseNativeControl::new(parent, ctrl_id),
events: ListViewEvents::new(parent, ctrl_id),
context_menu: context_menu_id.map(
|id| HINSTANCE::NULL.LoadMenu(IdStr::Id(id)).unwrap()
.GetSubMenu(0).unwrap(), ),
_pin: PhantomPinned,
_data: PhantomData,
},
),
);
let self2 = new_self.clone();
parent.as_ref().before_user_on().wm(co::WM::INITDIALOG, move |_, _| {
self2.create(OptsResz::Dlg(resize_behavior))?;
Ok(WmRet::NotHandled)
});
new_self.default_message_handlers(parent.as_ref(), ctrl_id);
new_self
}
fn create(&self, opts_resz: OptsResz<&ListViewOpts>) -> SysResult<()> {
match opts_resz {
OptsResz::Wnd(opts) => {
let mut pos = POINT::new(opts.position.0, opts.position.1);
let mut sz = SIZE::new(opts.size.0 as _, opts.size.1 as _);
multiply_dpi_or_dtu(
self.0.base.parent(), Some(&mut pos), Some(&mut sz))?;
self.0.base.create_window(
"SysListView32", None, pos, sz,
opts.window_ex_style,
opts.window_style | opts.list_view_style.into(),
)?;
if opts.list_view_ex_style != co::LVS_EX::NoValue {
self.set_extended_style(true, opts.list_view_ex_style);
}
self.columns().add(&opts.columns);
},
OptsResz::Dlg(_) => self.0.base.create_dlg()?,
}
let style: co::LVS = self.hwnd().style().into();
self.hwnd().set_style(style | co::LVS::SHAREIMAGELISTS);
self.0.base.parent()
.add_to_layout_arranger(self.hwnd(), opts_resz.resize_behavior())
}
fn default_message_handlers(&self, parent: &Base, ctrl_id: u16) {
let self2 = self.clone();
self.on_subclass().wm_get_dlg_code(move |p| {
if !p.is_query && p.vkey_code == co::VK::RETURN {
let mut nmlvkd = NMLVKEYDOWN::default();
nmlvkd.hdr.hwndFrom = unsafe { self2.hwnd().raw_copy() };
nmlvkd.hdr.set_idFrom(self2.ctrl_id());
nmlvkd.hdr.code = co::LVN::KEYDOWN.into();
nmlvkd.wVKey = co::VK::RETURN;
unsafe {
self2.hwnd()
.GetAncestor(co::GA::PARENT)
.unwrap()
.SendMessage(wm::Notify { nmhdr: &mut nmlvkd.hdr }); }
}
let dlgc_system = unsafe {
self2.hwnd().DefSubclassProc::<wm::GetDlgCode>(p.into())
};
Ok(dlgc_system)
});
let self2 = self.clone();
self.on_subclass().wm(co::WM::NOTIFY, move |p| {
let wm_nfy = wm::Notify::from_generic_wm(p);
if wm_nfy.nmhdr.code >= co::HDN::GETDISPINFO.into()
&& wm_nfy.nmhdr.code <= co::HDN::BEGINDRAG.into() {
unsafe {
self2.hwnd()
.GetAncestor(co::GA::PARENT)
.unwrap()
.SendMessage(wm_nfy); }
}
Ok(WmRet::NotHandled) });
let self2 = self.clone();
parent.before_user_on().wm_notify(ctrl_id, co::LVN::KEYDOWN, move |p| {
let lvnk = unsafe { p.cast_nmhdr::<NMLVKEYDOWN>() };
let has_ctrl = GetAsyncKeyState(co::VK::CONTROL);
let has_shift = GetAsyncKeyState(co::VK::SHIFT);
if has_ctrl && lvnk.wVKey == co::VK::CHAR_A { self2.items().select_all(true);
} else if lvnk.wVKey == co::VK::APPS { self2.show_context_menu(false, has_ctrl, has_shift);
}
Ok(WmRet::HandledOk)
});
let self2 = self.clone();
parent.before_user_on().wm_notify(ctrl_id, co::NM::RCLICK, move |p| {
let nmia = unsafe { p.cast_nmhdr::<NMITEMACTIVATE>() };
let has_ctrl = nmia.uKeyFlags.has(co::LVKF::CONTROL);
let has_shift = nmia.uKeyFlags.has(co::LVKF::SHIFT);
self2.show_context_menu(true, has_ctrl, has_shift);
Ok(WmRet::HandledOk)
});
let self2 = self.clone();
parent.after_user_on().wm_notify(ctrl_id, co::LVN::DELETEITEM, move |p| {
let nmlv = unsafe { p.cast_nmhdr::<NMLISTVIEW>() };
self2.items()
.get(nmlv.iItem as _)
.data_lparam()
.map(|pdata| {
let _ = unsafe { Rc::from_raw(pdata) }; });
Ok(WmRet::HandledOk)
});
let self2 = self.clone();
parent.after_user_on().wm(co::WM::DESTROY, move |_, _| {
[co::LVSIL::NORMAL, co::LVSIL::SMALL, co::LVSIL::STATE, co::LVSIL::GROUPHEADER]
.iter()
.for_each(|lvsil| {
self2.image_list(*lvsil).map(|hil| { let _ = unsafe { ImageListDestroyGuard::new(hil.raw_copy()) };
});
});
Ok(WmRet::NotHandled)
});
}
#[must_use]
pub const fn columns(&self) -> ListViewColumns<'_, T> {
ListViewColumns::new(self)
}
#[must_use]
pub fn context_menu(&self) -> Option<&HMENU> {
self.0.context_menu.as_ref()
}
#[must_use]
pub fn image_list(&self, kind: co::LVSIL) -> Option<&HIMAGELIST> {
unsafe {
self.hwnd()
.SendMessage(lvm::GetImageList { kind })
}.map(|hil| {
let hil_ptr = &hil as *const HIMAGELIST;
unsafe { &*hil_ptr }
})
}
#[must_use]
pub const fn items(&self) -> ListViewItems<'_, T> {
ListViewItems::new(self)
}
#[must_use]
pub fn current_view(&self) -> co::LV_VIEW {
unsafe { self.hwnd().SendMessage(lvm::GetView {}) }
}
pub fn set_current_view(&self, view: co::LV_VIEW) {
unsafe {
self.hwnd()
.SendMessage(lvm::SetView { view })
}.unwrap();
}
pub fn set_extended_style(&self, set: bool, ex_style: co::LVS_EX) {
unsafe {
self.hwnd().SendMessage(lvm::SetExtendedListViewStyle {
mask: ex_style,
style: if set { ex_style } else { co::LVS_EX::NoValue },
});
}
}
pub fn set_image_list(&self,
kind: co::LVSIL,
himagelist: ImageListDestroyGuard,
) -> Option<ImageListDestroyGuard>
{
let mut himagelist = himagelist;
let hil = himagelist.leak();
unsafe {
self.hwnd()
.SendMessage(lvm::SetImageList { kind, himagelist: Some(hil) })
.map(|prev_hil| ImageListDestroyGuard::new(prev_hil))
}
}
pub fn set_redraw(&self, can_redraw: bool) {
unsafe {
self.hwnd()
.SendMessage(wm::SetRedraw { can_redraw });
}
}
fn show_context_menu(&self,
follow_cursor: bool,
has_ctrl: bool,
has_shift: bool,
) {
let hmenu = match self.0.context_menu.as_ref() {
Some(h) => h,
None => return, };
let menu_pos = if follow_cursor { let menu_pos = self.hwnd().ScreenToClient(
GetCursorPos().unwrap(), ).unwrap(); match self.items().hit_test(menu_pos) {
Some(item_over) => {
if !has_ctrl && !has_shift {
item_over.select(true); item_over.focus();
}
},
None => self.items().select_all(false), }
self.hwnd().SetFocus(); menu_pos
} else { let focused_opt = self.items().focused();
if focused_opt.is_some() && focused_opt.unwrap().is_visible() {
let focused = focused_opt.unwrap();
let rc_item = focused.rect(co::LVIR::BOUNDS);
POINT::new(rc_item.left + 16,
rc_item.top + (rc_item.bottom - rc_item.top) / 2)
} else { POINT::new(6, 10) }
};
hmenu.track_popup_menu_at_point(
menu_pos, &self.hwnd().GetParent().unwrap(), self.hwnd())
.unwrap();
}
}
pub struct ListViewOpts {
pub position: (i32, i32),
pub size: (u32, u32),
pub list_view_style: co::LVS,
pub list_view_ex_style: co::LVS_EX,
pub window_style: co::WS,
pub window_ex_style: co::WS_EX,
pub ctrl_id: u16,
pub resize_behavior: (Horz, Vert),
pub context_menu: Option<HMENU>,
pub columns: Vec<(String, u32)>,
}
impl Default for ListViewOpts {
fn default() -> Self {
Self {
position: (0, 0),
size: (50, 50),
list_view_style: co::LVS::REPORT | co::LVS::NOSORTHEADER | co::LVS::SHOWSELALWAYS,
list_view_ex_style: co::LVS_EX::NoValue,
window_style: co::WS::CHILD | co::WS::VISIBLE | co::WS::TABSTOP | co::WS::GROUP,
window_ex_style: co::WS_EX::LEFT | co::WS_EX::CLIENTEDGE,
ctrl_id: 0,
resize_behavior: (Horz::None, Vert::None),
context_menu: None,
columns: Vec::<(String, u32)>::new(),
}
}
}
impl ResizeBehavior for &ListViewOpts {
fn resize_behavior(&self) -> (Horz, Vert) {
self.resize_behavior
}
}
impl AutoCtrlId for ListViewOpts {
fn ctrl_id_mut(&mut self) -> &mut u16 {
&mut self.ctrl_id
}
}